package second_assignment;

import java.util.LinkedList;
import java.util.Vector;

import first_assignment.*;
import first_assignment.Consts_1st.*;

/**
 * 
 * @author Peretz Oded, Castel Maayan, Farchy Ofir.
 */
public class SystemInitialization {

	// the vector a.k.a F (in the assignment) holding number of copies per
	// movie.
	private Vector<Movie> F_Recorded;
	private Vector<Movie> F_Library;
	private Vector<Movie> F_RecentlyBroadcast;
	
	private int[] copiesRecorded;
	private int[] copiesLibrary;
	private int[] copiesRecentlyBroadcast;
	
	private int roundRobinIndex;

	/**
	 * c'tor. System initialization, makes sure the F vector is created
	 * according to the given probabilities
	 * 
	 * @param moviesAmount
	 *            - an array holding movie count for each movie request type.
	 * @param probability
	 *            - an array holding the request type probabilities.
	 * @param popularityDistrib
	 *            - an array holding the distribution factor for each request
	 *            type.
	 */
	public SystemInitialization(Vector<Integer> moviesAmount,
			Vector<Double> probability, Vector<Integer> popularityDistrib,
			Vector<DistributionCreator> distCreators) {
		Vector<Integer> tempVector;
		int totalMovieCount = 0;
		copiesRecorded = new int[Consts_2nd.M_Recorded];
		copiesLibrary = new int[Consts_2nd.M_Library];
		copiesRecentlyBroadcast = new int[Consts_2nd.M_RecentlyBroadcast];

		// validation
		if (moviesAmount.size() != moviesAmount.size()
				|| moviesAmount.size() != moviesAmount.size()) {
			System.out
					.println("System failed to initialize, erroneous vectors.");
			return;
		}
		// calculates the size of M
		for (int i = 0; i < moviesAmount.size(); ++i) {
			totalMovieCount += moviesAmount.get(i);
		}
		// a temporary vector for division of copies among Mi's
		tempVector = CalculateApprtionment(probability,
				Consts_2nd.SystemCapacity, Consts_2nd.SystemCapacity);

		// the creation of the F vector itself.		
		F_Library = InitMovieType(moviesAmount.get(0), popularityDistrib.get(0),
				tempVector.get(0), distCreators.get(0), "Library_Day-1", 0);
		F_Recorded = InitMovieType(moviesAmount.get(1), popularityDistrib.get(1),
				tempVector.get(1), distCreators.get(1), "Recorded_Day-1", 1);
		F_RecentlyBroadcast = InitMovieType(moviesAmount.get(2), popularityDistrib.get(2),
				tempVector.get(2), distCreators.get(2), "RecentlyBroadcast_Day-1", 2);
		
		InitCopiesVectors();
	}
	
	public void InitCopiesVectors()
	{
		for (int i = 0; i < Consts_2nd.M_Library; ++i){
			copiesLibrary[i] = F_Library.get(i).copies;
		}
		
		for (int i = 0; i < Consts_2nd.M_Recorded; ++i){
			copiesRecorded[i] = F_Recorded.get(i).copies;
		}

		for (int i = 0; i < Consts_2nd.M_RecentlyBroadcast; ++i){
			copiesRecentlyBroadcast[i] = F_RecentlyBroadcast.get(i).copies;
		}
	}

	/**
	 * for the given set of probabilities check that they add up to 1
	 * 
	 * @param groupProbability
	 *            - set of probabilities to check
	 * @return true for valid set of probabilities; false otherwise.
	 */
	private Boolean isProbabilityValid(Vector<Double> groupProbability) {
		Double probabilitySum = 0.0;
		for (Double probability : groupProbability) {
			probabilitySum += probability;
		}
		return ((probabilitySum + 0.99 < 2.0) && (probabilitySum - 0.99 > 0.0));
	}

	/**
	 * initializes a single movie type (request type), allocates copies for the
	 * movies in it.
	 * 
	 * @param numOfMovies
	 *            - number of movies in the request type
	 * @param popularityDistrib
	 *            - the popularity distribution factor.
	 * @param copiesAmount
	 *            - amount of slots available for allocation
	 * @return the allocation vector
	 */
	public Vector<Movie> InitMovieType(int numOfMovies,
			int popularityDistrib, int copiesAmount,
			DistributionCreator distribCreator, String prefix, int movieType) {
	    if (Consts_2nd.MaxMovieCopies*numOfMovies < copiesAmount) { return null; }

		Vector<Double> popGroupProbabilities = new Vector<Double>(numOfMovies);
		Vector<Integer> tempApportionment;
		Vector<Movie> totalApportionment = new Vector<Movie>(numOfMovies);

		// create a vector containing pop-class' probabilities, then the
		// allocation.
		for (int i = 0; i < distribCreator.getPopularityClassNumber(); ++i) {
			int size = distribCreator.getPopularityClassSize(i);
			for (int j = 0; j < size; ++j) {
				popGroupProbabilities.add(j, ((double) distribCreator.getPopularityClassProbabilty(i))
						/ (size * 100.0));
			}
		}

		tempApportionment = CalculateApprtionment(popGroupProbabilities,
				copiesAmount, Consts_2nd.MaxMovieCopies);
		
		for (int i=0;  i < tempApportionment.size(); ++i) {
		    int movieCopies = tempApportionment.get(i);
		    if (Consts_2nd.MaxMovieCopies < movieCopies) {
		        System.out.println("ERROR: movie at location " + i + " got " + movieCopies + " copies.");
		    }
		    Movie newMovie = new Movie(movieCopies, prefix + "." + i);
		    newMovie.movieType = movieType;
			totalApportionment.add(i, newMovie);
		}
		return totalApportionment;
	}

	/**
	 * the vector F_Library as defined in the assignment, holding for each movie 
	 * in the Library section the
	 * amount of copies to be inserted in the system.
	 * 
	 * @return the copies array for all F_Library movies.
	 */
	public Vector<Movie> getFLibrary() {
		return F_Library;
	}
	
	/**
	 * @return the copies array for all F_Recorded movies.
	 */
	public Vector<Movie> getFRecorded() {
		return F_Recorded;
	}
	
	/**
	 * @return the copies array for all F_RecentlyBroadcast movies.
	 */
	public Vector<Movie> getFRecentlyBroadcast() {
		return F_RecentlyBroadcast;
	}
	
	public int[] getRecentlyBroadcastCopies() {
		return copiesRecentlyBroadcast;
	}
	public int[] getRecordedCopies() {
		return copiesRecorded;
	}
	public int[] getLibraryCopies() {
		return copiesLibrary;
	}

	/**
	 * Stores the copies of each movie in the clusters
	 * 
	 * @param clusterArray
	 *            - the system in which the movies will be inserted.
	 */
	public void StoreCopies(ClusterType[] clusterArray) {
		roundRobinIndex = 1;

		// insert one copy of each movie to the root cluster
		for (int i=F_Library.size()-1; i>=0; --i) {
			clusterArray[0].AddMovie(F_Library.get(i).movieName);
		}
		for (int i=F_RecentlyBroadcast.size()-1; i>=0; --i) {
			clusterArray[0].AddMovie(F_RecentlyBroadcast.get(i).movieName);
		}
		for (int i=F_Recorded.size()-1; i>=0; --i) {
			clusterArray[0].AddMovie(F_Recorded.get(i).movieName);
		}
		
		int libraryCounter = Consts_2nd.M_Library - 1;
		int recordedCounter = Consts_2nd.M_Recorded - 1;
		int recentlyCounter = Consts_2nd.M_RecentlyBroadcast - 1;
		
		while (libraryCounter >= 0 || recentlyCounter >= 0 || recordedCounter >= 0) {
			if (recentlyCounter >= 0){
				AddMovieToClusters(F_RecentlyBroadcast.get(recentlyCounter), clusterArray);
				--recentlyCounter;
			}
			else if (recordedCounter >= 0){
				AddMovieToClusters(F_Recorded.get(recordedCounter), clusterArray);
				--recordedCounter;
			}
			else if (libraryCounter >= 0){
				AddMovieToClusters(F_Library.get(libraryCounter), clusterArray);
				--libraryCounter;
			}
		}
		
		int countCopies = 0;
		for (ClusterType cluster : clusterArray) {
			countCopies += cluster.AmountOfCopies();
		}
		System.out.println("Amount of copies in the system is: " + countCopies);
	}

	public boolean AddMovieToClusters(Movie movieToAdd, ClusterType[] clusterArray) {
		int copies = movieToAdd.copies - 1;
		int tmpCluster = roundRobinIndex;
		String movieName = movieToAdd.movieName;
		while (copies > 0) {
			if (clusterArray[roundRobinIndex].AddMovie(movieName) != Consts_2nd.eINVALID) {
				--copies;
				tmpCluster = roundRobinIndex;
			}

			roundRobinIndex = (roundRobinIndex + 1) % (Consts_2nd.NumberOfNonRootClusters) + 1;
			if (tmpCluster == roundRobinIndex) {
				roundRobinIndex = (roundRobinIndex + 1) % (Consts_2nd.NumberOfNonRootClusters) + 1;
				break;
			}
		}
		while ((copies > 0) && (clusterArray[0].AddMovie(movieName) != Consts_2nd.eINVALID)) {
			--copies;
		}
		int i = roundRobinIndex, j=0;
		// try to rearrange the cluster and make room for the movie.
		while (0 < copies) {
			if (clusterArray[i].ForceMovieInsertion(movieToAdd) != 0) {
				--copies;
				j = 0;
			}
			i = (i + 1) % (Consts_2nd.NumberOfNonRootClusters + 1);
			if (Consts_2nd.NumberOfNonRootClusters + 1 == j++) {
				break;
			}
			
		}
		
		j = 0;
		i = roundRobinIndex;
		DiskType availDisk = null;
		boolean flag = true;
		while (0 < copies) {
			availDisk = clusterArray[i].CanAccommodateMovie(movieToAdd.movieName); 
			if (null != availDisk) {
				for (int k=0; k < clusterArray.length; ++k) {
					DiskType nonFullDisk = clusterArray[k].getNonFullDisk();
					if (null != nonFullDisk) {
						String movieToMove = availDisk.getUniqueMovie(nonFullDisk);
						flag &= availDisk.DeleteMovie(movieToMove);
						flag &= (0 <= clusterArray[k].AddMovie(movieToMove))? true : false ;
						flag &= availDisk.AddMovie(movieToAdd.movieName);
						if (!flag) {
						    System.out.println("ERROR: failed on adding a movie (inter-cluster).");
						}
						--copies;
						j = 0;
						k = clusterArray.length;
					}
				}
			}
			i = (i + 1) % (Consts_2nd.NumberOfNonRootClusters + 1);
			if (Consts_2nd.NumberOfNonRootClusters + 1 == j++) {
				System.out.println("(RORG) finished inter-cluster reorganization (left:" + copies + ")");
				break;
			}
		}
		return ((copies == 0) && (movieToAdd.copies > 1));
	}

	/**
	 * creates a valid apportionment, based on Hamilton's algorithm.
	 * 
	 * @param groupProbability
	 *            - the probabilities on which we rely on during the
	 *            apportionment
	 * @param amountOfCopies
	 *            - amount of slots (copies) available for allocation
	 * @param maxAllocation
	 *            - the maximal amount of copies for each movie.
	 * @return the apportionment vector.
	 */
	private Vector<Integer> CalculateApprtionment(
			Vector<Double> groupProbability, int amountOfCopies,
			int maxAllocation) {
		// Data validation
		if ((maxAllocation * groupProbability.size() < amountOfCopies)
				|| (!isProbabilityValid(groupProbability))) {
			System.out.println("ERROR: Invalid apportionment information.");
			return null;
		}
		Vector<Integer> apportionment = new Vector<Integer>(groupProbability
				.size());
		int leftAmount = amountOfCopies;

		// first allocation to ensure at least one copy.
		for (int i = 0; i < groupProbability.size(); ++i) {
			apportionment.add(i, 1);
			--leftAmount;
		}

		// allocation corrections, increases the amount of copies for
		// each movie group based on the remainder.
		// where the remainder is:
		// (probability * amount_of_slots - currently_allocated)
		double maxRemainder, currRemainder;
		LinkedList<Integer> indexArray = new LinkedList<Integer>();
		while (0 < leftAmount) {
		    indexArray.clear();
			maxRemainder = Integer.MIN_VALUE;
			for (int i = 0; i < groupProbability.size(); ++i) {
				currRemainder = groupProbability.get(i) * amountOfCopies
						- apportionment.get(i);
				if ((maxRemainder < currRemainder)
						&& (maxAllocation > apportionment.get(i))) {
					maxRemainder = currRemainder;
					indexArray.clear();
					indexArray.add(i);
				}
				else if ((maxRemainder == currRemainder) 
				        && (maxAllocation > apportionment.get(i))) {
				    indexArray.add(i);
				}
			}
			for (Integer index : indexArray) {
			    if (0 == leftAmount--) { break; }
			    apportionment.set(index, apportionment.get(index) + 1);
			}
		}
		return apportionment;
	}
}